Tout comme leaflet pour les cartes, il existe de nombreux outils R dédiés à la visualisation interactive. Nous en présentons quelques uns dans cette partie.

Représentations classiques avec rAmCharts et plotly

Le package rAmCharts est très utilie pour donner un carcatère interactif à des représentations graphiques standards (nuages de points, séries temporelles, histogrammes…). Ce package a été fait dans l’esprit d’utiliser les fonctions graphiques de R en utilisant le préfixe am. La syntaxe est très proche de celle des fonctions graphiques standards. On a par exemple :

library(rAmCharts)
amHist(iris$Petal.Length)

amPlot(iris, col = colnames(iris)[1:2], type = c("l", "st"), 
       zoom = TRUE, legend = TRUE)

amBoxplot(iris)

boxplot(iris)

plotly permet de faire des choses semblables avec avec une syntaxe specifique. Les commandes plotly se décomposent essentiellement en 3 parties :

  • le type de représentation graphique (plot_ly}) ;
  • les ajouts que l’on souhaite effectuer (add_trace) ;
  • la gestion de la fenêtre graphique (axes, titres…) (layout).

On trouvera un descriptif complet de ces 3 composantes ici. On peut par exemple tracer un nuage de points avec une droite de régression avec

library(tidyverse)
library(plotly)
n <- 100
X <- runif(n,-5,5)
Y <- 2+3*X+rnorm(n,0,1)
D <- data.frame(X,Y)
model <- lm(Y~X,data=D)
D %>% plot_ly(x=~X,y=~Y) %>% add_markers(type="scatter",mode="markers",marker=list(color="red"),name="Nuage") %>%
  add_trace(y=fitted(model),type="scatter",mode='lines',name="Régression",line=list(color="blue")) %>% 
  layout(title="Régression",xaxis=list(title="abscisse"),yaxis=list(title="ordonnées"))

Contrairement à ggplot, plotly permet de faire de la 3D. Par exemple

plot_ly(z = volcano, type = "surface")

plot_ly(z = volcano, type = "contour")

De plus, la fonction ggplotly permet de convertir un objet ggplot en plotly :

p <- ggplot(iris)+aes(x=Species,y=Sepal.Length)+geom_boxplot()+theme_classic()
ggplotly(p)

Réseaux avec visNetwork

De nombreuses données peuvent être visualisées à l’aide d’un réseau, notamment lorsqu’il s’agit de représenter des connections entre individus. Un individu est alors représentés par un noeud et les individus connectés sont reliés par des arrêtes. Le package (igraph)[http://kateto.net/networks-r-igraph] propose une visualisation statique d’un réseau. Pour donner un caractère dynamique à cetype de représentation, on pourra utiliser le package visNetwork. Une représentation standard visNetwork s’effectue en spécifiant les noeuds et connections d’un graphe :

nodes <- data.frame(id = 1:15, label = paste("Id", 1:15),group=sample(LETTERS[1:3], 15, replace = TRUE))
edges <- data.frame(from = trunc(runif(15)*(15-1))+1,to = trunc(runif(15)*(15-1))+1)
library(visNetwork)
data.frame(nodes=nodes,edges=edges)
visNetwork(nodes,edges)

visNetwork(nodes, edges) %>% visOptions(highlightNearest = TRUE)

visNetwork(nodes, edges) %>% visOptions(highlightNearest = TRUE, nodesIdSelection = TRUE)

visNetwork(nodes, edges) %>% visOptions(selectedBy = "group")

Exercice 1

Pour le jeu de données iris on effectuera les graphes suivants en rAmCharts et plotly.

  1. Nuage de points représentant les longeurs et largeurs de Sépales. On utilisera une couleur différente en fonction de l’espèce.

  2. Boxplot permettant de visualiser la distribution de la variable Petal.Length en fonction de l’espèce.

Exercice 2

Le fichier temperat.txt contient les températures moyennes mensuelles de 15 villes, ainsi que leurs géolocalisations.

  1. Importer le fichier

  2. Représenter les villes sur une carte de France.

  3. A l’aide de la fonction heatmap, visualiser des groupes de villes qui comportent des similitudes en terme de températures mensuelles.

  4. Effectuer une classification ascendante hiérarchique pour créer 3 ou 4 clusters de villes. On pourra utiliser hclust.

  5. Visualiser les groupes créés sur une carte de France.

  6. Visualiser les groupes créés à l’aide d’un réseau effectué avec visNetwork.

Dashboard

Un tableau de bord permet de visualiser “facilement” et “rapidement” divers graphes et/ou résumés statistiques en lien avec une problématique donnée. Sur R le package flexdashboard permet de construire de tels tableaux de bord. On trouvera un descriptif précis de ce package à cette url : https://rmarkdown.rstudio.com/flexdashboard/.

Exercice 3

On considère le jeu de données ozone.txt

  1. Représenter le nuage de points maxO3 vs T12 en ajoutant un lisseur. On fera les graphes en ggplot et rAmCharts et plotly (en utilisant ggplotly par exemple).

  2. Même question pour l’histogramme de la variable maxO3.

  3. Construire le modèle linéaire permettant d’expliquer maxO3 par les autres variables. Calculer les résidus studentisés (rstudent) et visualiser ces résidus en fonction de la variable maxO3. Là encore on pourra ajouter un lisseur sur le graphe.

  4. Construire un dashboard permettant de visualiser sur une page :

  • le jeu de données (on pourra utiliser la fonction datatable du package DT)
  • L’histogramme et le nuage de points calculés dans les questions 1 et 2.

On utilisera les menus : File -> Rmardown -> From Template -> Flex Dashboard.

  1. Ajouter un onglet permettant de visualiser les coefficients du modèle linéaire ainsi que le graphe des résidus effectués à la questions 3.

  2. Ajouter un nouvel onglet permettant de choisir les variables explicatives dans le modèle linéaire. Pour ce faire, il faudra utiliser des commandes Shiny, par exemple

checkboxGroupInput("variable",
                   label="Choisir la variable",
                   choices=names(df)[-1],
                   selected=list("T9"))

Pour les variables choisies, on affichera dans ce nouvel onglet les coefficients du modèle linéaire ainsi que le graphe des résidus studentisés.

  1. On pourra terminer en essayant d’obtenir un dashboard proche de (ou mieux que) celui présenté à l’url https://lrouviere.shinyapps.io/dashboard/

R shiny

Exercice 4

On considère le jeu de données SAheart du package bestglm.

  1. A l’aide du package rAmCharts, représenter les histogrammes des variables quantitatives du jeu de données ainsi que les boxplot de ces variables en fonction de la variable chd.

  2. Créer une application shiny qui permette de

  • choisir une variable parmi les variables quantitatives du jeu de données. On pourra utiliser radioButtons avec l’argument
choices=names(SAheart)[sapply(SAheart,class)=="numeric"]
  • visualiser l’histogramme, puis le boxplot en fonction de chd de la variable sélectionnée. Ces graphiques devront être faits avec rAmCharts. On pourra utiliser amChartsOutput.

L’application demandée pourra être proche de celle disponible à cette url https://lrouviere.shinyapps.io/desc_app/

Exercice 5

  1. On considère le même jeu de données. On veut expliquer la variable chd. Créer une application shiny qui permette de choisir entre une lda, une régression logistique et une régression logistique où on utilise l’AIC ou le BIC pour sélectionner les variables. L’application devra afficher le résumé (summary) du modèle sélectionné. On pourra utiliser
selectInput("methode","Choisir la méthode",selected=c("Logistique","LDA"),multiple=TRUE,choices = c("Logistique","BIC","AIC","LDA"))

et l’application pourra ressembler à https://lrouviere.shinyapps.io/choix_mod_app/

  1. Expliquer la fonction trace_roc qui se trouve sur la page web du cours.

  2. Ajouter à l’application un slider qui permette de choisir la taille de l’échantillon d’apprentissage sur lequel on ajuste le modèle. On pourra utiliser

sliderInput(inputId="napp",
            label="Taille de l'échantillon d'apprentissage",
            min = 2,max = nrow(SAheart),
            value =round(nrow(SAheart)/2))
  1. Rappeler l’utilité de la fonction reactive.

  2. Ajouter la courbe ROC du (ou des) modèle(s) choisi(s). Cette courbe devra se mettre à jour lorsqu’on modifie le modèle et la taille de l’échantillon d’apprentissage.

On pourra s’inspirer de l’application https://lrouviere.shinyapps.io/roc_app/

Exercice 6

On pourra aussi essayer de réaliser une application pour visualiser les stations velib à Rennes comme celle ci https://lrouviere.shinyapps.io/velib/. On récupérera les données sur le site de Rennes métropole : https://data.rennesmetropole.fr/explore/dataset/etat-des-stations-le-velo-star-en-temps-reel/export/

LS0tCnRpdGxlOiAiVmlzdWFsaXNhdGlvbiBpbnTDqXJhY3RpdmUgZXQgU2hpbnkiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY3NzOiB+L0Ryb3Bib3gvRklDSElFUlNfU1RZTEUvc3R5bGVzLmNzcwogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKIyAgaHRtbF9kb2N1bWVudDogZGVmYXVsdAojICBydW50aW1lOiBzaGlueQojZWRpdG9yX29wdGlvbnM6IAojICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQoKClRvdXQgY29tbWUgKipsZWFmbGV0KiogcG91ciBsZXMgY2FydGVzLCBpbCBleGlzdGUgZGUgbm9tYnJldXggb3V0aWxzICoqUioqIGTDqWRpw6lzIMOgIGxhIHZpc3VhbGlzYXRpb24gaW50ZXJhY3RpdmUuIE5vdXMgZW4gcHLDqXNlbnRvbnMgcXVlbHF1ZXMgdW5zIGRhbnMgY2V0dGUgcGFydGllLgoKIyBSZXByw6lzZW50YXRpb25zIGNsYXNzaXF1ZXMgYXZlYyBgckFtQ2hhcnRzYCBldCBgcGxvdGx5YAoKTGUgcGFja2FnZSBbckFtQ2hhcnRzXShodHRwczovL2RhdGFzdG9ybS1vcGVuLmdpdGh1Yi5pby9pbnRyb2R1Y3Rpb25fcmFtY2hhcnRzLykgZXN0IHRyw6hzIHV0aWxpZSBwb3VyIGRvbm5lciB1biBjYXJjYXTDqHJlIGludGVyYWN0aWYgw6AgZGVzIHJlcHLDqXNlbnRhdGlvbnMgZ3JhcGhpcXVlcyBzdGFuZGFyZHMgKG51YWdlcyBkZSBwb2ludHMsIHPDqXJpZXMgdGVtcG9yZWxsZXMsIGhpc3RvZ3JhbW1lcy4uLikuIENlIHBhY2thZ2UgYSDDqXTDqSBmYWl0IGRhbnMgbCdlc3ByaXQgZCd1dGlsaXNlciBsZXMgZm9uY3Rpb25zIGdyYXBoaXF1ZXMgZGUgKipSKiogZW4gdXRpbGlzYW50IGxlIHByw6lmaXhlICoqYW0qKi4gTGEgc3ludGF4ZSBlc3QgdHLDqHMgcHJvY2hlIGRlIGNlbGxlIGRlcyBmb25jdGlvbnMgZ3JhcGhpcXVlcyBzdGFuZGFyZHMuIE9uIGEgcGFyIGV4ZW1wbGUgOgoKCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHJBbUNoYXJ0cykKYW1IaXN0KGlyaXMkUGV0YWwuTGVuZ3RoKQphbVBsb3QoaXJpcywgY29sID0gY29sbmFtZXMoaXJpcylbMToyXSwgdHlwZSA9IGMoImwiLCAic3QiKSwgCiAgICAgICB6b29tID0gVFJVRSwgbGVnZW5kID0gVFJVRSkKYW1Cb3hwbG90KGlyaXMpCmJveHBsb3QoaXJpcykKYGBgCgoKW3Bsb3RseV0oaHR0cHM6Ly9wbG90Lmx5L3IvKSBwZXJtZXQgZGUgZmFpcmUgZGVzIGNob3NlcyBzZW1ibGFibGVzIGF2ZWMgYXZlYyB1bmUgc3ludGF4ZSBzcGVjaWZpcXVlLiBMZXMgY29tbWFuZGVzICoqcGxvdGx5Kiogc2UgZMOpY29tcG9zZW50IGVzc2VudGllbGxlbWVudCBlbiAzIHBhcnRpZXMgOgoKKiBsZSB0eXBlIGRlIHJlcHLDqXNlbnRhdGlvbiBncmFwaGlxdWUgKCoqcGxvdF9seSoqfSkgOwoqIGxlcyBham91dHMgcXVlIGwnb24gc291aGFpdGUgZWZmZWN0dWVyICgqKmFkZF90cmFjZSoqKSA7CiogbGEgZ2VzdGlvbiBkZSBsYSBmZW7DqnRyZSBncmFwaGlxdWUgKGF4ZXMsIHRpdHJlcy4uLikgKCoqbGF5b3V0KiopLgoKT24gdHJvdXZlcmEgdW4gZGVzY3JpcHRpZiBjb21wbGV0IGRlIGNlcyAzIGNvbXBvc2FudGVzIFtpY2ldKGh0dHBzOi8vcGxvdC5seS9yL3JlZmVyZW5jZS8pLiBPbiBwZXV0IHBhciBleGVtcGxlIHRyYWNlciB1biBudWFnZSBkZSBwb2ludHMgYXZlYyB1bmUgZHJvaXRlIGRlIHLDqWdyZXNzaW9uIGF2ZWMKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHBsb3RseSkKbiA8LSAxMDAKWCA8LSBydW5pZihuLC01LDUpClkgPC0gMiszKlgrcm5vcm0obiwwLDEpCkQgPC0gZGF0YS5mcmFtZShYLFkpCm1vZGVsIDwtIGxtKFl+WCxkYXRhPUQpCkQgJT4lIHBsb3RfbHkoeD1+WCx5PX5ZKSAlPiUgYWRkX21hcmtlcnModHlwZT0ic2NhdHRlciIsbW9kZT0ibWFya2VycyIsbWFya2VyPWxpc3QoY29sb3I9InJlZCIpLG5hbWU9Ik51YWdlIikgJT4lCiAgYWRkX3RyYWNlKHk9Zml0dGVkKG1vZGVsKSx0eXBlPSJzY2F0dGVyIixtb2RlPSdsaW5lcycsbmFtZT0iUsOpZ3Jlc3Npb24iLGxpbmU9bGlzdChjb2xvcj0iYmx1ZSIpKSAlPiUgCiAgbGF5b3V0KHRpdGxlPSJSw6lncmVzc2lvbiIseGF4aXM9bGlzdCh0aXRsZT0iYWJzY2lzc2UiKSx5YXhpcz1saXN0KHRpdGxlPSJvcmRvbm7DqWVzIikpCmBgYAoKQ29udHJhaXJlbWVudCDDoCAqKmdncGxvdCoqLCAqKnBsb3RseSoqIHBlcm1ldCBkZSBmYWlyZSBkZSBsYSAzRC4gUGFyIGV4ZW1wbGUKCmBgYHtyfQpwbG90X2x5KHogPSB2b2xjYW5vLCB0eXBlID0gInN1cmZhY2UiKQpwbG90X2x5KHogPSB2b2xjYW5vLCB0eXBlID0gImNvbnRvdXIiKQpgYGAKCgpEZSBwbHVzLCBsYSBmb25jdGlvbiAqKmdncGxvdGx5KiogcGVybWV0IGRlIGNvbnZlcnRpciB1biBvYmpldCAqKmdncGxvdCoqIGVuICoqcGxvdGx5KiogOgpgYGB7cn0KcCA8LSBnZ3Bsb3QoaXJpcykrYWVzKHg9U3BlY2llcyx5PVNlcGFsLkxlbmd0aCkrZ2VvbV9ib3hwbG90KCkrdGhlbWVfY2xhc3NpYygpCmdncGxvdGx5KHApCmBgYAoKCiMgUsOpc2VhdXggYXZlYyB2aXNOZXR3b3JrCgpEZSBub21icmV1c2VzIGRvbm7DqWVzIHBldXZlbnQgw6p0cmUgdmlzdWFsaXPDqWVzIMOgIGwnYWlkZSBkJ3VuIHLDqXNlYXUsIG5vdGFtbWVudCBsb3JzcXUnaWwgcydhZ2l0IGRlIHJlcHLDqXNlbnRlciBkZXMgY29ubmVjdGlvbnMgZW50cmUgaW5kaXZpZHVzLiBVbiBpbmRpdmlkdSBlc3QgYWxvcnMgcmVwcsOpc2VudMOpcyBwYXIgdW4gbm9ldWQgZXQgbGVzIGluZGl2aWR1cyBjb25uZWN0w6lzIHNvbnQgcmVsacOpcyBwYXIgZGVzIGFycsOqdGVzLiBMZSBwYWNrYWdlIChpZ3JhcGgpW2h0dHA6Ly9rYXRldG8ubmV0L25ldHdvcmtzLXItaWdyYXBoXSBwcm9wb3NlIHVuZSB2aXN1YWxpc2F0aW9uIHN0YXRpcXVlIGQndW4gcsOpc2VhdS4gUG91ciBkb25uZXIgdW4gY2FyYWN0w6hyZSBkeW5hbWlxdWUgw6AgY2V0eXBlIGRlIHJlcHLDqXNlbnRhdGlvbiwgb24gcG91cnJhIHV0aWxpc2VyIGxlIHBhY2thZ2UgW3Zpc05ldHdvcmtdKGh0dHBzOi8vZGF0YXN0b3JtLW9wZW4uZ2l0aHViLmlvL3Zpc05ldHdvcmsvaW50ZXJhY3Rpb24uaHRtbCkuIFVuZSByZXByw6lzZW50YXRpb24gc3RhbmRhcmQgKip2aXNOZXR3b3JrKiogcydlZmZlY3R1ZSBlbiBzcMOpY2lmaWFudCBsZXMgbm9ldWRzIGV0IGNvbm5lY3Rpb25zIGQndW4gZ3JhcGhlIDoKCmBgYHtyfQpub2RlcyA8LSBkYXRhLmZyYW1lKGlkID0gMToxNSwgbGFiZWwgPSBwYXN0ZSgiSWQiLCAxOjE1KSxncm91cD1zYW1wbGUoTEVUVEVSU1sxOjNdLCAxNSwgcmVwbGFjZSA9IFRSVUUpKQplZGdlcyA8LSBkYXRhLmZyYW1lKGZyb20gPSB0cnVuYyhydW5pZigxNSkqKDE1LTEpKSsxLHRvID0gdHJ1bmMocnVuaWYoMTUpKigxNS0xKSkrMSkKbGlicmFyeSh2aXNOZXR3b3JrKQpkYXRhLmZyYW1lKG5vZGVzPW5vZGVzLGVkZ2VzPWVkZ2VzKQp2aXNOZXR3b3JrKG5vZGVzLGVkZ2VzKQp2aXNOZXR3b3JrKG5vZGVzLCBlZGdlcykgJT4lIHZpc09wdGlvbnMoaGlnaGxpZ2h0TmVhcmVzdCA9IFRSVUUpCnZpc05ldHdvcmsobm9kZXMsIGVkZ2VzKSAlPiUgdmlzT3B0aW9ucyhoaWdobGlnaHROZWFyZXN0ID0gVFJVRSwgbm9kZXNJZFNlbGVjdGlvbiA9IFRSVUUpCnZpc05ldHdvcmsobm9kZXMsIGVkZ2VzKSAlPiUgdmlzT3B0aW9ucyhzZWxlY3RlZEJ5ID0gImdyb3VwIikKYGBgCgoKIyMjIEV4ZXJjaWNlIDEKClBvdXIgbGUgamV1IGRlIGRvbm7DqWVzICoqaXJpcyoqIG9uIGVmZmVjdHVlcmEgbGVzIGdyYXBoZXMgc3VpdmFudHMgZW4gKipyQW1DaGFydHMqKiBldCAqKnBsb3RseSoqLgoKMS4gTnVhZ2UgZGUgcG9pbnRzIHJlcHLDqXNlbnRhbnQgbGVzIGxvbmdldXJzIGV0IGxhcmdldXJzIGRlIFPDqXBhbGVzLiBPbiB1dGlsaXNlcmEgdW5lIGNvdWxldXIgZGlmZsOpcmVudGUgZW4gZm9uY3Rpb24gZGUgbCdlc3DDqGNlLgoKCjIuIEJveHBsb3QgcGVybWV0dGFudCBkZSB2aXN1YWxpc2VyIGxhIGRpc3RyaWJ1dGlvbiBkZSBsYSB2YXJpYWJsZSAqKlBldGFsLkxlbmd0aCoqIGVuIGZvbmN0aW9uIGRlIGwnZXNww6hjZS4KCgoKIyMjIEV4ZXJjaWNlIDIKCkxlIGZpY2hpZXIgKip0ZW1wZXJhdC50eHQqKiBjb250aWVudCBsZXMgdGVtcMOpcmF0dXJlcyBtb3llbm5lcyBtZW5zdWVsbGVzIGRlIDE1IHZpbGxlcywgYWluc2kgcXVlIGxldXJzIGfDqW9sb2NhbGlzYXRpb25zLgoKMS4gSW1wb3J0ZXIgbGUgZmljaGllcgoKCjIuIFJlcHLDqXNlbnRlciBsZXMgdmlsbGVzIHN1ciB1bmUgY2FydGUgZGUgRnJhbmNlLgoKCgozLiBBIGwnYWlkZSBkZSBsYSBmb25jdGlvbiAqKmhlYXRtYXAqKiwgdmlzdWFsaXNlciBkZXMgZ3JvdXBlcyBkZSB2aWxsZXMgcXVpIGNvbXBvcnRlbnQgZGVzIHNpbWlsaXR1ZGVzIGVuIHRlcm1lIGRlIHRlbXDDqXJhdHVyZXMgbWVuc3VlbGxlcy4KCgo0LiBFZmZlY3R1ZXIgdW5lIGNsYXNzaWZpY2F0aW9uIGFzY2VuZGFudGUgaGnDqXJhcmNoaXF1ZSBwb3VyIGNyw6llciAzIG91IDQgY2x1c3RlcnMgZGUgdmlsbGVzLiBPbiBwb3VycmEgdXRpbGlzZXIgKipoY2x1c3QqKi4KCgo1LiBWaXN1YWxpc2VyIGxlcyBncm91cGVzIGNyw6nDqXMgc3VyIHVuZSBjYXJ0ZSBkZSBGcmFuY2UuCgoKNi4gVmlzdWFsaXNlciBsZXMgZ3JvdXBlcyBjcsOpw6lzIMOgIGwnYWlkZSBkJ3VuIHLDqXNlYXUgZWZmZWN0dcOpIGF2ZWMgKip2aXNOZXR3b3JrKiouCgoKCiMgRGFzaGJvYXJkCgpVbiB0YWJsZWF1IGRlIGJvcmQgcGVybWV0IGRlIHZpc3VhbGlzZXIgImZhY2lsZW1lbnQiIGV0ICJyYXBpZGVtZW50IiBkaXZlcnMgZ3JhcGhlcyBldC9vdSByw6lzdW3DqXMgc3RhdGlzdGlxdWVzIGVuIGxpZW4gYXZlYyB1bmUgcHJvYmzDqW1hdGlxdWUgZG9ubsOpZS4gU3VyICoqUioqIGxlIHBhY2thZ2UgKipmbGV4ZGFzaGJvYXJkKiogcGVybWV0IGRlIGNvbnN0cnVpcmUgZGUgdGVscyB0YWJsZWF1eCBkZSBib3JkLiBPbiB0cm91dmVyYSB1biBkZXNjcmlwdGlmIHByw6ljaXMgZGUgY2UgcGFja2FnZSDDoCBjZXR0ZSB1cmwgOiBbaHR0cHM6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20vZmxleGRhc2hib2FyZC9dKGh0dHBzOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tL2ZsZXhkYXNoYm9hcmQvKS4KCiMjIyBFeGVyY2ljZSAzCgpPbiBjb25zaWTDqHJlIGxlIGpldSBkZSBkb25uw6llcyAqKm96b25lLnR4dCoqCgoxLiBSZXByw6lzZW50ZXIgbGUgbnVhZ2UgZGUgcG9pbnRzICoqbWF4TzMgdnMgVDEyKiogZW4gYWpvdXRhbnQgdW4gbGlzc2V1ci4gT24gZmVyYSBsZXMgZ3JhcGhlcyBlbiAqKmdncGxvdCoqIGV0ICoqckFtQ2hhcnRzKiogZXQgKipwbG90bHkqKiAoZW4gdXRpbGlzYW50ICoqZ2dwbG90bHkqKiBwYXIgZXhlbXBsZSkuCgoKCjIuIE3Dqm1lIHF1ZXN0aW9uIHBvdXIgbCdoaXN0b2dyYW1tZSBkZSBsYSB2YXJpYWJsZSAqKm1heE8zKiouCgoKMy4gQ29uc3RydWlyZSBsZSBtb2TDqGxlIGxpbsOpYWlyZSBwZXJtZXR0YW50IGQnZXhwbGlxdWVyICoqbWF4TzMqKiBwYXIgbGVzIGF1dHJlcyB2YXJpYWJsZXMuIENhbGN1bGVyIGxlcyByw6lzaWR1cyBzdHVkZW50aXPDqXMgKCoqcnN0dWRlbnQqKikgZXQgdmlzdWFsaXNlciBjZXMgcsOpc2lkdXMgZW4gZm9uY3Rpb24gZGUgbGEgdmFyaWFibGUgKiptYXhPMyoqLiBMw6AgZW5jb3JlIG9uIHBvdXJyYSBham91dGVyIHVuIGxpc3NldXIgc3VyIGxlIGdyYXBoZS4KCgoKNC4gQ29uc3RydWlyZSB1biAqKmRhc2hib2FyZCoqIHBlcm1ldHRhbnQgZGUgdmlzdWFsaXNlciBzdXIgdW5lIHBhZ2UgOiAKCiAgKiBsZSBqZXUgZGUgZG9ubsOpZXMgKG9uIHBvdXJyYSB1dGlsaXNlciBsYSBmb25jdGlvbiAqKmRhdGF0YWJsZSoqIGR1IHBhY2thZ2UgKipEVCoqKQogICogTCdoaXN0b2dyYW1tZSBldCBsZSBudWFnZSBkZSBwb2ludHMgY2FsY3Vsw6lzIGRhbnMgbGVzIHF1ZXN0aW9ucyAxIGV0IDIuCiAgCk9uIHV0aWxpc2VyYSBsZXMgbWVudXMgOiAqKkZpbGUgLT4gUm1hcmRvd24gLT4gRnJvbSBUZW1wbGF0ZSAtPiBGbGV4IERhc2hib2FyZCoqLgoKNS4gQWpvdXRlciB1biBvbmdsZXQgcGVybWV0dGFudCBkZSB2aXN1YWxpc2VyIGxlcyBjb2VmZmljaWVudHMgZHUgbW9kw6hsZSBsaW7DqWFpcmUgYWluc2kgcXVlIGxlIGdyYXBoZSBkZXMgcsOpc2lkdXMgZWZmZWN0dcOpcyDDoCBsYSBxdWVzdGlvbnMgMy4KCjYuIEFqb3V0ZXIgdW4gbm91dmVsIG9uZ2xldCBwZXJtZXR0YW50IGRlIGNob2lzaXIgbGVzIHZhcmlhYmxlcyBleHBsaWNhdGl2ZXMgZGFucyBsZSBtb2TDqGxlIGxpbsOpYWlyZS4gUG91ciBjZSBmYWlyZSwgaWwgZmF1ZHJhIHV0aWxpc2VyIGRlcyBjb21tYW5kZXMgKipTaGlueSoqLCBwYXIgZXhlbXBsZQoKYGBge3IsZXZhbD1GQUxTRX0KY2hlY2tib3hHcm91cElucHV0KCJ2YXJpYWJsZSIsCiAgICAgICAgICAgICAgICAgICBsYWJlbD0iQ2hvaXNpciBsYSB2YXJpYWJsZSIsCiAgICAgICAgICAgICAgICAgICBjaG9pY2VzPW5hbWVzKGRmKVstMV0sCiAgICAgICAgICAgICAgICAgICBzZWxlY3RlZD1saXN0KCJUOSIpKQpgYGAKClBvdXIgbGVzIHZhcmlhYmxlcyBjaG9pc2llcywgb24gYWZmaWNoZXJhIGRhbnMgY2Ugbm91dmVsIG9uZ2xldCBsZXMgY29lZmZpY2llbnRzIGR1IG1vZMOobGUgbGluw6lhaXJlIGFpbnNpIHF1ZSBsZSBncmFwaGUgZGVzIHLDqXNpZHVzIHN0dWRlbnRpc8Opcy4KCjcuIE9uIHBvdXJyYSB0ZXJtaW5lciBlbiBlc3NheWFudCBkJ29idGVuaXIgdW4gKipkYXNoYm9hcmQqKiBwcm9jaGUgZGUgKG91IG1pZXV4IHF1ZSkgY2VsdWkgcHLDqXNlbnTDqSDDoCBsJ3VybCBbaHR0cHM6Ly9scm91dmllcmUuc2hpbnlhcHBzLmlvL2Rhc2hib2FyZC9dKGh0dHBzOi8vbHJvdXZpZXJlLnNoaW55YXBwcy5pby9kYXNoYm9hcmQvKQoKIyBSIHNoaW55CgoKCiMjIyBFeGVyY2ljZSA0CgpPbiBjb25zaWTDqHJlIGxlIGpldSBkZSBkb25uw6llcyAqU0FoZWFydCogZHUgcGFja2FnZSAqKmJlc3RnbG0qKi4KCjEuIEEgbCdhaWRlIGR1IHBhY2thZ2UgKipyQW1DaGFydHMqKiwgcmVwcsOpc2VudGVyIGxlcyBoaXN0b2dyYW1tZXMgZGVzIHZhcmlhYmxlcyBxdWFudGl0YXRpdmVzIGR1IGpldSBkZSBkb25uw6llcyBhaW5zaSBxdWUgbGVzIGJveHBsb3QgZGUgY2VzIHZhcmlhYmxlcyBlbiBmb25jdGlvbiBkZSBsYSB2YXJpYWJsZSAqKmNoZCoqLgoKCgoyLiBDcsOpZXIgdW5lIGFwcGxpY2F0aW9uIHNoaW55IHF1aSBwZXJtZXR0ZSBkZSAKCiAgKiBjaG9pc2lyIHVuZSB2YXJpYWJsZSBwYXJtaSBsZXMgdmFyaWFibGVzIHF1YW50aXRhdGl2ZXMgZHUgamV1IGRlIGRvbm7DqWVzLiBPbiBwb3VycmEgdXRpbGlzZXIgKipyYWRpb0J1dHRvbnMqKiBhdmVjIGwnYXJndW1lbnQKCmBgYHtyLCBldmFsPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmNob2ljZXM9bmFtZXMoU0FoZWFydClbc2FwcGx5KFNBaGVhcnQsY2xhc3MpPT0ibnVtZXJpYyJdCmBgYAoKICAqIHZpc3VhbGlzZXIgbCdoaXN0b2dyYW1tZSwgcHVpcyBsZSBib3hwbG90IGVuIGZvbmN0aW9uIGRlICpjaGQqIGRlIGxhIHZhcmlhYmxlIHPDqWxlY3Rpb25uw6llLiBDZXMgZ3JhcGhpcXVlcyBkZXZyb250IMOqdHJlIGZhaXRzIGF2ZWMgKipyQW1DaGFydHMqKi4gT24gcG91cnJhIHV0aWxpc2VyICoqYW1DaGFydHNPdXRwdXQqKi4KCkwnYXBwbGljYXRpb24gZGVtYW5kw6llIHBvdXJyYSDDqnRyZSBwcm9jaGUgZGUgY2VsbGUgZGlzcG9uaWJsZSDDoCBjZXR0ZSB1cmwgW2h0dHBzOi8vbHJvdXZpZXJlLnNoaW55YXBwcy5pby9kZXNjX2FwcC9dKGh0dHBzOi8vbHJvdXZpZXJlLnNoaW55YXBwcy5pby9kZXNjX2FwcC8pCgoKCiMjIyBFeGVyY2ljZSA1CgoxLiBPbiBjb25zaWTDqHJlIGxlIG3Dqm1lIGpldSBkZSBkb25uw6llcy4gT24gdmV1dCBleHBsaXF1ZXIgbGEgdmFyaWFibGUgKipjaGQqKi4gQ3LDqWVyIHVuZSBhcHBsaWNhdGlvbiBzaGlueSBxdWkgcGVybWV0dGUgZGUgY2hvaXNpciBlbnRyZSB1bmUgKipsZGEqKiwgdW5lICoqcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSoqIGV0IHVuZSByw6lncmVzc2lvbiBsb2dpc3RpcXVlIG/DuSBvbiB1dGlsaXNlIGwnKipBSUMqKiBvdSBsZSAqKkJJQyoqIHBvdXIgc8OpbGVjdGlvbm5lciBsZXMgdmFyaWFibGVzLiBMJ2FwcGxpY2F0aW9uIGRldnJhIGFmZmljaGVyIGxlIHLDqXN1bcOpICgqc3VtbWFyeSopIGR1IG1vZMOobGUgc8OpbGVjdGlvbm7DqS4gT24gcG91cnJhIHV0aWxpc2VyCgpgYGB7ciwgZXZhbD1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpzZWxlY3RJbnB1dCgibWV0aG9kZSIsIkNob2lzaXIgbGEgbcOpdGhvZGUiLHNlbGVjdGVkPWMoIkxvZ2lzdGlxdWUiLCJMREEiKSxtdWx0aXBsZT1UUlVFLGNob2ljZXMgPSBjKCJMb2dpc3RpcXVlIiwiQklDIiwiQUlDIiwiTERBIikpCmBgYAoKZXQgbCdhcHBsaWNhdGlvbiBwb3VycmEgcmVzc2VtYmxlciDDoCBbaHR0cHM6Ly9scm91dmllcmUuc2hpbnlhcHBzLmlvL2Nob2l4X21vZF9hcHAvXShodHRwczovL2xyb3V2aWVyZS5zaGlueWFwcHMuaW8vY2hvaXhfbW9kX2FwcC8pCgoKCjIuIEV4cGxpcXVlciBsYSBmb25jdGlvbiAqKnRyYWNlX3JvYyoqIHF1aSBzZSB0cm91dmUgc3VyIGxhIHBhZ2Ugd2ViIGR1IGNvdXJzLgoKCgozLiBBam91dGVyIMOgIGwnYXBwbGljYXRpb24gdW4gc2xpZGVyIHF1aSBwZXJtZXR0ZSBkZSBjaG9pc2lyIGxhIHRhaWxsZSBkZSBsJ8OpY2hhbnRpbGxvbiBkJ2FwcHJlbnRpc3NhZ2Ugc3VyIGxlcXVlbCBvbiBhanVzdGUgbGUgbW9kw6hsZS4gT24gcG91cnJhIHV0aWxpc2VyCgpgYGB7ciwgZXZhbD1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpzbGlkZXJJbnB1dChpbnB1dElkPSJuYXBwIiwKICAgICAgICAgICAgbGFiZWw9IlRhaWxsZSBkZSBsJ8OpY2hhbnRpbGxvbiBkJ2FwcHJlbnRpc3NhZ2UiLAogICAgICAgICAgICBtaW4gPSAyLG1heCA9IG5yb3coU0FoZWFydCksCiAgICAgICAgICAgIHZhbHVlID1yb3VuZChucm93KFNBaGVhcnQpLzIpKQpgYGAKCjQuIFJhcHBlbGVyIGwndXRpbGl0w6kgZGUgbGEgZm9uY3Rpb24gKipyZWFjdGl2ZSoqLiAKCgo1LiBBam91dGVyIGxhIGNvdXJiZSBST0MgZHUgKG91IGRlcykgbW9kw6hsZShzKSBjaG9pc2kocykuIENldHRlIGNvdXJiZSBkZXZyYSBzZSBtZXR0cmUgw6Agam91ciBsb3JzcXUnb24gbW9kaWZpZSBsZSBtb2TDqGxlIGV0IGxhIHRhaWxsZSBkZSBsJ8OpY2hhbnRpbGxvbiBkJ2FwcHJlbnRpc3NhZ2UuCgpPbiBwb3VycmEgcydpbnNwaXJlciBkZSBsJ2FwcGxpY2F0aW9uIFtodHRwczovL2xyb3V2aWVyZS5zaGlueWFwcHMuaW8vcm9jX2FwcC9dKGh0dHBzOi8vbHJvdXZpZXJlLnNoaW55YXBwcy5pby9yb2NfYXBwLykKCgojIyMgRXhlcmNpY2UgNgoKCk9uIHBvdXJyYSBhdXNzaSBlc3NheWVyIGRlIHLDqWFsaXNlciB1bmUgYXBwbGljYXRpb24gcG91ciB2aXN1YWxpc2VyIGxlcyBzdGF0aW9ucyB2ZWxpYiDDoCBSZW5uZXMgY29tbWUgY2VsbGUgY2kgW2h0dHBzOi8vbHJvdXZpZXJlLnNoaW55YXBwcy5pby92ZWxpYi9dKGh0dHBzOi8vbHJvdXZpZXJlLnNoaW55YXBwcy5pby92ZWxpYi8pLiBPbiByw6ljdXDDqXJlcmEgbGVzIGRvbm7DqWVzIHN1ciBsZSBzaXRlIGRlIFJlbm5lcyBtw6l0cm9wb2xlIDogW2h0dHBzOi8vZGF0YS5yZW5uZXNtZXRyb3BvbGUuZnIvZXhwbG9yZS9kYXRhc2V0L2V0YXQtZGVzLXN0YXRpb25zLWxlLXZlbG8tc3Rhci1lbi10ZW1wcy1yZWVsL2V4cG9ydC9dKGh0dHBzOi8vZGF0YS5yZW5uZXNtZXRyb3BvbGUuZnIvZXhwbG9yZS9kYXRhc2V0L2V0YXQtZGVzLXN0YXRpb25zLWxlLXZlbG8tc3Rhci1lbi10ZW1wcy1yZWVsL2V4cG9ydC8pCg==